#!/usr/bin/python2.6

import sys
import socket
import time
import re
import random
from optparse  import OptionParser
from threading import Thread, Timer
from collections import deque


class msg_server(Thread):
	
    def __init__(self, handler, port=3721):
        Thread.__init__(self)
        sock = socket.socket(
                        socket.AF_INET, socket.SOCK_DGRAM)
        self.sock = sock
        sock.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
        self.port = port
        self.handler = handler

        try:
            sock.bind(('',port))
        except:
            sys.stderr.write("Can't bind to %d\n" % port)
            sys.exit(-1)
        sys.stdout.write("msg server listening on port %d\n"% port)


    def run(self):
        while 1:
            try:
                data, addr = self.sock.recvfrom(1024)
                print 'Got \'%s\' from %s:%s' %  (data, addr[0],addr[1])
                self.handler.process_msg(data)
            except socket.error, e:
                print "Recv Error"

                        
class msg_client:
    def __init__(self):
        sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
        self.sock = sock

    def send(self, msg, addr):
        if msg!='':
            try:
                self.sock.sendto(msg, addr)
            except socket.error, e:
                sys.stderr.write("sending error\n")
        else:
            print "empty message!"

			
class file_server:
    def __init__(self, nodes, file='fs.log'):
        self.clients = nodes[1:]
        self.server = nodes[0]
	print self.clients
	print self.server
        self.msg_srv = msg_server(handler = self, port = self.server[1])
        self.msg_clt = msg_client()
        self.msg_srv.start()
        self.file = file
        f = open(self.file, 'w')
        f.close()
	
    def process_msg(self, msg):
        if re.match('^(.+):(.+)$',msg):
            q = re.match('^(.+):(.+)$',msg)
            cmd = q.group(1)
            content = q.group(2)
        else:
            print 'Message Error--%s' % msg
            exit
        if cmd == 'WRT':
            f = open(self.file, 'a')
            f.write(content + '\n')
            f.close()
            req_src = int(content.split()[1])
            self.msg_clt.send('ACK:FS', self.clients[req_src])
        else:
            print 'Message Error--%s' % msg


class file_client:
    def __init__(self, nodes, id):
        self.clients = nodes[1:]
        self.server = nodes[0]
        self.id = id
	print id
	print self.clients
	print self.server
        self.msg_srv = msg_server(handler = self, port = self.clients[id][1])
        self.msg_clt = msg_client()
        self.msg_srv.start()
        self.time = [id, 0]
        # record replies for all local requests, key: time[1], value: rpl list
        self.rpl_received = dict()
        # local request time queue
        self.req_local = deque([])
        # not yet answered reqeust, content: request time
        self.req_received = []

    # if time1 < time2, return true
    def before_time(self, time1, time2):
        if time1[1] < time2[1]:
            return True
        elif time1[1] == time2[1]:
            if time1[0] <= time2[0]:
                return True
        return False

    def process_msg(self, msg):
        if re.match('^(.+):(.+)$',msg):
            q = re.match('^(.+):(.+)$',msg)
            cmd = q.group(1)
            content = q.group(2)
        else:
            print 'Message Error--%s' % msg
            exit()
        if cmd == 'REQ':
            items = content.split(',')
            req_src = int(items[0])
            time_counter = int(items[1])
            # update local time
            if time_counter > self.time[1]:
                self.time[1] = time_counter
            
            incoming_req_time = [req_src, time_counter]
            print 'receive request (%d, %d)' % (req_src, time_counter) 
            
            if len(self.req_local) == 0:
                self.msg_clt.send('RPL:%d,%d' % (time_counter, self.id), self.clients[req_src])
                print 'send RPL to request (%d, %d)' % (req_src, time_counter) 
            else:
                local_req_time = self.req_local[0]
                if self.before_time(incoming_req_time, local_req_time):
                    self.msg_clt.send('RPL:%d,%d' % (time_counter, self.id), self.clients[req_src])
                    print 'send RPL to (%d, %d) request' % (req_src, time_counter)
                else:
                    self.req_received.append(incoming_req_time)
                    print 'put request (%d, %d) in the list' % (req_src, time_counter) 
                   
            
        elif cmd == 'RPL':
            items = content.split(',')
            req_id = int(items[0])
            rpl_from = int(items[1])
            # received RPL of request req_id from client rpl_from
	    if req_id not in self.rpl_received:
		self.rpl_received[req_id] = [rpl_from]
	    else:
                self.rpl_received[req_id].append(rpl_from)
            # the request is at the head of the queue
            if self.req_local[0][1] == req_id:
                # check whether receiving PRL from all clients
                if len(self.rpl_received[req_id]) == len(self.clients):
                    self.msg_clt.send('WRT:client %d append %d %d' % (self.id, self.req_local[0][0], self.req_local[0][1]), self.server)
                    print 'request (%d, %d) write to FS' % (self.id, req_id)
                    self.rpl_received.pop(req_id)

        # update local time
        elif cmd =='EVT':
            items = content.split(',')
            evt_src = items[0]
            time_counter = int(items[1])
            if time_counter > self.time[1]:
                self.time[1] = time_counter

        elif cmd == 'ACK':
            finish_req = self.req_local.popleft()
            if len(self.req_local) == 0:
                for i in range(len(self.req_received)):
                    time_counter = self.req_received[i][1]
                    req_src = self.req_received[i][0]
                    self.msg_clt.send('RPL:%d,%d' % (time_counter, self.id), self.clients[req_src])
                    print 'send RPL to request (%d, %d)' % (req_src, time_counter)
                self.req_received = []
            else:
                pruned_req = []
                local_req_time = self.req_local[0]
                for i in range(len(self.req_received)):
                    incoming_req_time = self.req_received[i]
                    if self.before_time(incoming_req_time, local_req_time):
                        time_counter = self.req_received[i][1]
                        req_src = self.req_received[i][0]
                        self.msg_clt.send('RPL:%d,%d' % (time_counter, self.id), self.clients[req_src])
                        print 'send RPL to request (%d, %d)' % (req_src, time_counter)
                    else:
                        pruned_req.append(self.req_received[i])
                self.req_received = pruned_req
                        
        else:
            print 'Message Error--%s' % msg


    # Send msg to another random client, for the purpose of generating causal relations among REQ events
    def evt_handler(self):
        clt_id = random.randint(0, len(self.clients)-1)
	if clt_id != self.id:
            self.msg_clt.send('EVT:%d,%d' % (self.time[0], self.time[1]), self.clients[clt_id])
            print 'send events to client %d' % clt_id
	self.evt_timer = Timer(random.randint(1, 8), self.evt_handler)
        self.evt_timer.start()

    def req_handler(self):
        self.time[1] += 1
        self.req_local.append(self.time)
        for i in range(len(self.clients)):
            self.msg_clt.send('REQ:%d,%d' % (self.time[0], self.time[1]), self.clients[i])
            print 'send REQ to client %d' % i
        self.req_timer = Timer(random.randint(1, 13), self.req_handler)
        self.req_timer.start()

    def run(self):
        time.sleep(10)
        self.evt_timer = Timer(random.randint(0, 8), self.evt_handler)
        self.evt_timer.start()

        self.req_timer = Timer(random.randint(0, 13), self.req_handler)
        self.req_timer.start()

        # time.sleep(random.randint(10, 30))


if __name__=='__main__':
	
	parser = OptionParser()
	parser.add_option("-f", "--file", 
			  dest="filename", default = "lamport.log",
	                  help="file name on the server")
	parser.add_option("-i", "--id",
	                  dest="id", default=-1, type = "int",
			  help="process id, -1 for server")
	parser.add_option("-c", "--config", type = "string",
			  dest="config_file", default = "lamport.config",
			  help="config file name")

	(options, args) = parser.parse_args()

	nodes=[]
	f=open(options.config_file)
	for line in f:
            q = line[:-1].split()
            node = (q[1],int(q[2]))
            nodes.append(node)
	print nodes
	if options.id==-1:
            proc = file_server(nodes, options.filename)
	else:
	    proc = file_client(nodes, options.id)
            proc.run()
